﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity.InterceptionExtension;
using System.Reflection;
using System.Threading;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using System.Configuration;
using Thallo.Configuration;
using System.IO;
using Thallo.Extention;
using System.Collections.Concurrent;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;

namespace Thallo {
    public sealed class Interceptor {
        public static readonly Interceptor Current = new Interceptor();

        private InstrumentationProvider Instrumentation { get; set; }


        private ThalloSection configSection;
        internal TypeDescriptorProvider Builder { get; private set; }
        IUnityContainer _container;

        private Interceptor() {
            _container = new UnityContainer();
            Builder = new TypeDescriptorProvider(_container);
            Instrumentation = new InstrumentationProvider();

            try {
                _container.LoadConfiguration("thallo");
            } catch (ConfigurationErrorsException ce) {
                throw new ConfigurationErrorsException("Unity配置错误，请确认配置文件中存在factory配置节", ce);
            } catch {
                throw;
            }

            Setup();
        }

        public void Setup() {
            Setup(ThalloSection.Current);
        }

        public void Setup(ThalloSection section) {
            configSection = section;
            Builder.Setup(section);
        }

        public object Wrap(object instance) {
            if (instance == null)
                return null;
            var type = instance.GetType();
            return _container.BuildUp(type, instance);
        }

        public T Wrap<T>(T instance) where T : class {
            return _container.BuildUp<T>(instance);
        }


        internal IMethodReturn BeforeProcess(IMethodInvocation inputs) {
            Loger.TraceInformation("Thallo\tbefore process\t{0}\t{1}",
                    inputs.MethodBase.DeclaringType.Name,
                    inputs.MethodBase.Name);

            var mi = InvocationContext.Current.MethodInfo;
            if (mi.MethodHandler != null)
                mi.MethodHandler.BeforeInvoke(inputs.MethodBase, inputs.Inputs.GetValues());

            if (mi.UpdateMode != UpdateMode.None) {
                var result = ProcessResult(inputs, mi);
                if (result != null)
                    return result;
            }

            return null;
        }

        internal void AfterProcess(IMethodInvocation inputs, IMethodReturn invoke, bool upate) {
            var mi = InvocationContext.Current.MethodInfo;
            if (mi.UpdateMode != UpdateMode.None && upate && mi.Method.ReturnType != typeof(void)) {
                AfterProcessResult(inputs, invoke, mi);
            }

            if (mi.MethodHandler != null)
                mi.MethodHandler.AfterInvoke(inputs.MethodBase, inputs.Inputs.GetValues());
            InvocationContext.Current.MethodInvoked();

            Loger.TraceInformation("Thallo\tafter process\t{0}\t{1}",
                    inputs.MethodBase.DeclaringType.Name,
                    inputs.MethodBase.Name);
        }

        #region private methods
        ReaderWriterLock _rw = new ReaderWriterLock();
        ConcurrentDictionary<RuntimeMethodHandle, HashSet<string>> _keys = new ConcurrentDictionary<RuntimeMethodHandle, HashSet<string>>();

        private HashSet<string> GetMethodCacheKeys(RuntimeMethodHandle mi) {
            HashSet<string> keys;
            var miKey = mi;
            _keys.TryGetValue(miKey, out keys);
            return keys;
        }

        private IMethodReturn ProcessResult(IMethodInvocation inputs, MethodDescriptor opa) {
            var item = GetCacheItem(inputs, opa);
            if (item == null)
                return null;

            return inputs.CreateMethodReturn(item.ReturnValue, item.Outputs ?? new object[] { });
        }

        private void AfterProcessResult(IMethodInvocation inputs, IMethodReturn invoke, MethodDescriptor mi) {
            var item = CreateMethodCacheItem(invoke);
            SetCacheItem(inputs, item, mi);
        }

        private MethodResult GetCacheItem(IMethodInvocation inputs, MethodDescriptor opa) {

            var val = InvocationContext.Current.GetValue();

            if (val != null) {
                Instrumentation.Hits();
                Loger.TraceInformation("Thallo\t get item from cache\t{0}\t{1}",
                                inputs.MethodBase.DeclaringType.FullName,
                                inputs.MethodBase.Name);
            } else {
                Instrumentation.Misses();
                Loger.TraceInformation("Thallo\t miss from cache\t{0}\t{1}",
                                inputs.MethodBase.DeclaringType.FullName,
                                inputs.MethodBase.Name);
            }

            return val;
        }

        private void SetCacheItem(IMethodInvocation inputs, MethodResult item,
                    MethodDescriptor opa) {

            BackingStoreManager.SyncCacheKeys(InvocationContext.Current.Identity);

            Loger.TraceInformation("Thallo\t push item to cache\t{0}\t{1}",
                                inputs.MethodBase.DeclaringType.FullName,
                                inputs.MethodBase.Name);


            InvocationContext.Current.SetValue(item);

            Instrumentation.Addes();

            Loger.TraceInformation("Thallo\t push item to cache\t{0}\t{1}",
                            inputs.MethodBase.DeclaringType.FullName,
                            inputs.MethodBase.Name);
        }

        private MethodResult CreateMethodCacheItem(IMethodReturn invoke) {
            var item = InvocationContext.Current.GetValue() ?? new MethodResult();
            item.ReturnValue = invoke.ReturnValue;
            if (invoke.Outputs.Count == 0) {
                return item;
            }
            var ary = new List<object>();
            for (int i = 0; i < invoke.Outputs.Count; i++) {
                var val = invoke.Outputs[i];
                ary.Add(val);
            }
            item.Outputs = ary.ToArray();
            return item;
        }
        
        #endregion
    }
}
